package pub.cleangao.mybatis.structure;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import pub.cleangao.common.util.JsonUtil;
import pub.cleangao.common.util.ReflectUtil;
import pub.cleangao.common.util.Scanner;
import pub.cleangao.mybatis.annotation.*;
import pub.cleangao.mybatis.cleavage.UseCleavage;
import pub.cleangao.mybatis.dao.CrudMapper;
import pub.cleangao.mybatis.meta.*;
import pub.cleangao.mybatis.runtime.Mode;
import pub.cleangao.mybatis.runtime.MybatisContext;
import pub.cleangao.mybatis.template.EnjoyEngine;
import pub.cleangao.mybatis.util.ClassUtil;
import pub.cleangao.mybatis.util.FieldUtil;
import pub.cleangao.mybatis.util.JavaTypeUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


@Slf4j
public class ClassFileScanner {


    /**
     * 根据entity包中的文件, 建立数据结构
     *
     * @param entityPackage entity包
     */
    public static void buildDataStructureBy(String entityPackage) {
        Set<Class<?>> entities = Scanner.lookupClassFile(entityPackage);
        if (entities.size() != 0) {
            for (Class<?> clazz : entities) {
                // 使用@Table注解标识
                if (ClassUtil.hasAnnotation(clazz, Table.class)) {
                    analysisEntity(clazz);
                }
            }
        }
    }


    /**
     * 实体分析
     */
    public static void analysisEntity(Class<?> clazz) {
        // 解析实体类
        Table table = ClassUtil.findAnnotation(clazz, Table.class);
        TableDefinition tableDefinition = new TableDefinition();
        tableDefinition.setTableName(table.value());
        tableDefinition.setEnableCache(table.enableCache());
        List<Field> fieldList = ReflectUtil.getDeclaredFields(clazz);
        List<ColumnDefinition> columnDefinitions = new ArrayList<>();
        List<ColumnDefinition> cleavageColumnsBy = new ArrayList<>();
        for (Field field : fieldList) {
            ColumnDefinition columnDefinition = new ColumnDefinition();
            // 解析每个字段
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            if (primaryKey != null) {
                tableDefinition.setPkColumn(columnDefinition);
                columnDefinition.setPrimaryKey(true);
                switch (primaryKey.strategy()) {
                    case AUTO:
                        columnDefinition.setPkAutoIncrease(true);
                        columnDefinition.setKeySeqName(primaryKey.keySeqName());
                        break;
                    case UUID:
                        columnDefinition.setPkUUID(true);
                        break;
                    case MANUAL:
                        columnDefinition.setPkManual(true);
                        break;
                    default:
                        break;
                }
            }
            LogicDelete logicDelete = field.getAnnotation(LogicDelete.class);
            if (logicDelete != null) {
                tableDefinition.setHasLogicDelete(true);
                tableDefinition.setLogicDeleteColumn(columnDefinition);
                columnDefinition.setLogicDeleteDefault(logicDelete.defaultValue());
                columnDefinition.setLogicDeletedValue(logicDelete.deletedValue());
                columnDefinition.setLogicDelete(true);
            }
            Version version = field.getAnnotation(Version.class);
            if (version != null) {
                tableDefinition.setHasVersion(true);
                tableDefinition.setVersionColumn(columnDefinition);
                columnDefinition.setVersion(true);
            }
            // 通用字段属性
            columnDefinition.setFieldName(field.getName());
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                columnDefinition.setColumnName(column.value());
                if (column.dateTimeAutoFill()) {
                    columnDefinition.setAutoFillTimeStamp(true);
                }
                int dbCleavageParamIndex = column.cleavageParamIndex();
                if (dbCleavageParamIndex > -1) {
                    columnDefinition.setCleavageParamIndex(dbCleavageParamIndex);
                    cleavageColumnsBy.add(columnDefinition);
                }
            }
            if (columnDefinition.getColumnName() == null || columnDefinition.getColumnName().isEmpty()) {
                String columnName = OrmConfig.camel2underline ? FieldUtil.camelToUnderline(field.getName()) : field.getName();
                columnDefinition.setColumnName(columnName);
            }
            columnDefinition.setFieldFullType(field.getType().getName());
            columnDefinition.setFieldType(field.getType().getSimpleName());
            columnDefinition.setJdbcType(JavaTypeUtil.convertToMyBatisJdbcType(field.getType().getSimpleName()));
            columnDefinition.calcIfDateTime();
            columnDefinitions.add(columnDefinition);
        }
        tableDefinition.setColumnDefinitions(columnDefinitions);
        tableDefinition.setCleavage(table.cleavage());
        tableDefinition.setCleavageClass(table.cleavageClass());
        tableDefinition.setCleavageColumnBy(cleavageColumnsBy);
        Database database = ClassUtil.findAnnotation(clazz, Database.class);
        if (database != null) {
            tableDefinition.setSchema(database.value());
            tableDefinition.setDialect(database.dialect());
            tableDefinition.setDataSourceKey(database.dataSourceKey());
        }
        MybatisContext.addTableDefinition(clazz, tableDefinition);
    }

    /**
     * 根据mapper包中的接口文件, 建立执行代理
     *
     * @param mapperPackage mapper所在包
     */
    public static void buildExecutorProxyBy(String mapperPackage) {
        Set<Class<?>> mappers = Scanner.lookupClassFile(mapperPackage);
        if (mappers.size() != 0) {
            for (Class<?> clazz : mappers) {
                // 是否继承自CrudMapper作为判断是否执行增强的根据
                // 1. 使用@Mapper注解
                // 2. 继承自CrudMapper
                if (Mode.DEBUG) {
                    System.out.println("check if enhance? ->[mapper]: " + clazz.getName() + " [isExtendsFrom]: " + ClassUtil.isExtendsFrom(clazz, CrudMapper.class) + " [hasAnnotation]: " + ClassUtil.hasAnnotation(clazz, Mapper.class)
                            + " |:" + String.valueOf(ClassUtil.isExtendsFrom(clazz, CrudMapper.class) & ClassUtil.hasAnnotation(clazz, Mapper.class)));
                }
                if (ClassUtil.isExtendsFrom(clazz, CrudMapper.class) && ClassUtil.hasAnnotation(clazz, Mapper.class)) {
                    String xmlFile = generateMapperXml(clazz);
                    // 3. 将生成的XML文件合并至MyBatis
                    analysisMapper(clazz);
                    Class<?> entityClass = ClassUtil.getSuperInterfaceGenericType(clazz, 0);
                    MybatisContext.mapperToEntity(clazz, entityClass);
                    MybatisContext.addXmlMapper(xmlFile, entityClass);
                    if (Mode.DEBUG) {
                        System.out.println("mapper2entity: " + clazz.getName() + " --> " + entityClass.getName());
                    }
                }
            }

        }
    }

    /**
     * Mapper接口分析
     */
    private static void analysisMapper(Class<?> mapperClass) {
        Method[] methods = mapperClass.getMethods();
        MapperInterfaceDefinition interfaceDefinition = new MapperInterfaceDefinition();
        Set<MapperMethodDefinition> methodDefinitions = new HashSet<>();
        for (Method method : methods) {
            MapperMethodDefinition methodDefinition = new MapperMethodDefinition();
            UseCleavage useCleavage = method.getAnnotation(UseCleavage.class);
            if (useCleavage != null) {
                methodDefinition.setCleavage(true);
                methodDefinition.setCleavageClass(useCleavage.cleavageClass());
            }
            methodDefinition.setMethodName(method.getName());
            if (method.getParameterCount() > 0) {
                methodDefinition.setParameterCount(method.getParameterCount());
                methodDefinition.setParamTypes(method.getParameterTypes());
                Parameter[] parameters = method.getParameters();
                String[] params = new String[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    params[i] = parameter.getName();
                }
                methodDefinition.setParamNames(params);
            }
            methodDefinitions.add(methodDefinition);
        }
        interfaceDefinition.setInterfaceName(mapperClass.getName());
        interfaceDefinition.setSimpleName(mapperClass.getSimpleName());
        interfaceDefinition.setMethodDefinitions(methodDefinitions);
        MybatisContext.addMapperDefinition(mapperClass, interfaceDefinition);
        if (Mode.DEBUG) {
            System.out.println("mapper-definition: " + mapperClass.getName() + "->" + JsonUtil.pojoToJson(interfaceDefinition));
        }
    }


    /**
     * 生成Mapper.xml文件
     *
     * @param clazz Mapper(interface)的Class类型
     */
    public static String generateMapperXml(Class<?> clazz) {
        Class<?> entityClass = ClassUtil.getSuperInterfaceGenericType(clazz, 0);
        EntityInformation entityInformation = MybatisContext.getEntityInfo(entityClass);
        TableDefinition tableDefinition = MybatisContext.getTableDefinition(entityClass);
        String xmlContent = EnjoyEngine.getTemplateText(clazz.getName(), tableDefinition, entityInformation);
        // 不再写入文件，直接存内存
        String targetFile = entityInformation.getSimpleClassName() + "Mapper.xml";
        MybatisContext.addMapperXmlContent(targetFile, xmlContent);
        return targetFile;
    }


}
